package com.ouroboroswiki.core.content.markup.processor;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.ouroboroswiki.core.AbstractContentRepository;
import com.ouroboroswiki.core.Content;
import com.ouroboroswiki.core.ContentException;
import com.ouroboroswiki.core.ContentPath;
import com.ouroboroswiki.core.ContentRepository;
import com.ouroboroswiki.core.ContentUtil;
import com.ouroboroswiki.core.ContentValidationResult;
import com.ouroboroswiki.core.Version;
import com.ouroboroswiki.core.VersionBuilder;

public class MarkupContentRepositoryProxy extends AbstractContentRepository {
	
	private static final Logger log = Logger.getLogger(MarkupContentRepositoryProxy.class.getName());
	
	private ContentRepository proxied;
	private List<MarkupAssociation> associations;
	private TransformerFactory transformerFactory;
	private DocumentBuilderFactory documentBuilderFactory;
	
	public MarkupContentRepositoryProxy( TransformerFactory transformerFactory, DocumentBuilderFactory documentBuilderFactory ) {
		this( null, transformerFactory, documentBuilderFactory );
	}	
	
	public MarkupContentRepositoryProxy( List<MarkupAssociation> associations, TransformerFactory transformerFactory, DocumentBuilderFactory documentBuilderFactory ) {
		this.associations = associations;
		this.transformerFactory = transformerFactory;
		this.documentBuilderFactory = documentBuilderFactory;
	}
	
	public void setProxied( ContentRepository proxied ) {
		this.proxied = proxied;
	}
	
	public void setAssociations( List<MarkupAssociation> associations ) {
		this.associations = associations;
	}
	

	@Override
	public boolean exists(Object principal, ContentPath path) throws ContentException {
		return proxied.exists(principal, path);
	}

	@Override
	public Content getContent(Object principal, ContentPath path, Version version, Map<String, Object> properties)
			throws ContentException {
		// TODO split out the version information
		Content content = proxied.getContent(principal, path, version, properties);
		ArrayList<ContentValidationResult> validations = new ArrayList<ContentValidationResult>();
		// load that into a DOM straight away
		try {
			//log.log( Level.INFO, content.toString() +":"+ ContentUtil.contentToString(content) );
			InputStream ins = ContentUtil.openInputStream(content);
			try {
				DocumentBuilder db = documentBuilderFactory.newDocumentBuilder();
				Document document = db.parse(ins);
				
				// TODO add a validation warning when duplicate child contents are added
				HashMap<String, Content> childContent = new HashMap<String, Content>();
				childContent.put(Content.CHILD_CONTENT_MAIN, content);
				
				for( int i=0; i<associations.size(); i++ ) {
					MarkupAssociation association = associations.get( i );
					XPathExpression xpathExpression = association.getXPathExpression();

					try {
						NodeList nodes;
						if( xpathExpression != null ) {
							nodes = (NodeList)xpathExpression.evaluate(
									document.getDocumentElement(), 
									XPathConstants.NODESET
							);							
						} else {
							nodes = (NodeList)association.getXPath().evaluate(
									association.getPath(), 
									document, 
									XPathConstants.NODESET
							);
						}
						if( nodes != null ) {
							for( int j=0; j<nodes.getLength(); j++ ) {
								Node node = nodes.item(j);
								List<Node> replacements = association.getHandler().replace(principal, (Element)node, document, childContent, path, version, validations, properties);
								Node parent = node.getParentNode();
								if( replacements != null ) {
									for( int k=0; k<replacements.size(); k++ ) {
										Node replacement = replacements.get( k );
										parent.insertBefore(replacement, node);
									}
								}
								parent.removeChild(node);
							}
						}
					} catch( XPathExpressionException ex ) {
						validations.add(
								new ContentValidationResult(
										ContentValidationResult.Level.Error, 
										"unable to evaluate xpath "+association, 
										ex
								)
						);
					}
				}
				
				MarkupContent result = new MarkupContent(document, childContent, transformerFactory);
				result.setValidationResults(validations);
				result.setMimeType(content.getMimeType());
				result.setReadable(true);
				result.setExists(content.exists());
				result.setUniqueName(ContentUtil.toString(path));
				result.setRepositoryName(this.getName());
				return result;
			} catch( ParserConfigurationException ex ) {
				throw new ContentException( "unable to create document builder "+documentBuilderFactory, ex );
			} catch( SAXException ex ) {
				throw new ContentException( "unable to parse "+content+" : "+ContentUtil.contentToString(content), ex );
			}finally {
				try {
					ins.close();
				} catch( Exception ex ) {
					log.log( Level.WARNING, "unable to close "+content, ex);
				}
			}
		} catch( IOException ex ) {
			throw new ContentException( "unable to open "+content, ex );
		}
	}

	@Override
	public Version buildVersion(Object principal, ContentPath path,
			String[] versionPath, VersionBuilder versionBuilder)
			throws ContentException {
		// TODO split out child information
		return proxied.buildVersion(principal, path, versionPath, versionBuilder);
	}

	public String getName() {
		return this.proxied.getName();
	}
	
}
